-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Introduce the Revisionable extension #2825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
758fae8
to
9428ca6
Compare
Having no username set in the |
Thanks, I've updated everything to account for that. |
25c3e45
to
6f21faf
Compare
b143ad5
to
f4bc83a
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2825 +/- ##
==========================================
+ Coverage 78.56% 78.95% +0.39%
==========================================
Files 169 182 +13
Lines 8803 9366 +563
==========================================
+ Hits 6916 7395 +479
- Misses 1887 1971 +84 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
761f013
to
8af0d92
Compare
What's the status of this PR? Anything I can do to help/test? Would like to see it merged soon! |
I don't think I have anything big to add to this PR at this point. It really just needs testing and making sure the idea is solid (especially as part of the rationale for a new extension versus trying to make a version of loggable that is DBAL 4 friendly is allowing both the old and new data to live side-by-side so an app can either keep that old data by either polyfilling the deprecated DBAL array type or migrating it into the new extension). |
99c5d53
to
5b3b1cb
Compare
I started trying to test/use this in an ODM configuration. Notables so far:
So at the moment I've not been able to create/trigger any revisions, I guess there is a way to enable this without EDIT: Yes, the
|
The loggable listener has to be open because of its Maybe it's too soon to go with a hard final listener, but I'm not going to take the "no I'm not going to open it up for inheritance" standpoint if there's a legitimate use case that a hard final blocks. Maybe somewhere down the road, something can be figured out for how interfaces can be built for the listeners (as right now the hook points into Doctrine are all Doctrine event listeners, so the interface is more so specifying the required events to subscribe to and less what a public-ish API looks like).
The new extension is more strictly typed given it's being written as new code with the PHP 7.4 minimum in mind, compared to all of the other extensions which were written in PHP 5 times;
I wanted to make the revision model a little more strict in terms of having valid state, hence the introduction of the static
That'd have to be done after this PR landed. Trying to shoot a PR off over there would just result in a PR with constantly failing builds, it's easier to handle that update after this change lands. For manual config in a Symfony app, you can take the loggable listener service in https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/frameworks/symfony.md#extensions-compatible-with-all-managers and change that to the revisionable listener (it'll hook into the same events so it's just changing the service ID and class name)
It'll get done before this merges. I did call out the needed mapping changes in the PR description, but the gist of it is you switch |
If action is no longer nullable does it make sense set a default then, to avoid e.g.
(or whatever, if the syntax is slightly off) |
# services.yaml
gedmo.mapping.driver.attribute:
class: Gedmo\Mapping\Driver\AttributeReader
gedmo.listener.revisionable:
class: Gedmo\Revisionable\RevisionableListener
tags:
- { name: doctrine.event_listener, event: 'onFlush' }
- { name: doctrine.event_listener, event: 'loadClassMetadata' }
- { name: doctrine.event_listener, event: 'postPersist' }
calls:
- [ setAnnotationReader, [ '@gedmo.mapping.driver.attribute' ] ]
# doctrine.yaml
orm:
# ...
entity_managers:
default:
# ...
mappings:
# ...
gedmo_loggable:
type: attribute
prefix: Gedmo\Loggable\Entity
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Entity/"
alias: GedmoLoggable # (optional) it will default to the name set for the mappingmapping
is_bundle: false
revisionable:
type: attribute
alias: GedmoRevisionable
prefix: Gedmo\Revisionable\Entity
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Revisionable/Entity"
is_bundle: false
# stof_doctrine_extensions.yaml
stof_doctrine_extensions:
default_locale: en_US
orm:
default:
timestampable: true
blameable: true
loggable: true
sortable: true <?php
declare(strict_types=1);
namespace App\Entity;
use App\Repository\VendorRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Gedmo\Mapping\Annotation\Loggable;
use Gedmo\Mapping\Annotation\Versioned;
#[Loggable]
#[ORM\Table(name: 'vendors')]
#[ORM\Entity(repositoryClass: VendorRepository::class)]
class Vendor
{
#[Column(name: 'vendor_id', type: Types::INTEGER)]
#[Id]
#[GeneratedValue(strategy: 'IDENTITY')]
private ?int $vendorId = null;
#[Versioned]
#[Column(name: 'name', type: Types::STRING, length: 255, unique: true)]
private string $name;
public function getVendorId(): ?int
{
return $this->vendorId;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
}
If I add <?php
declare(strict_types=1);
namespace App\Entity;
use App\Repository\VendorRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Gedmo\Mapping\Annotation\Loggable;
use Gedmo\Mapping\Annotation\Revisionable;
use Gedmo\Mapping\Annotation\Versioned;
#[Loggable]
#[Revisionable]
#[ORM\Table(name: 'vendors')]
#[ORM\Entity(repositoryClass: VendorRepository::class)]
class Vendor
{
#[Column(name: 'vendor_id', type: Types::INTEGER)]
#[Id]
#[GeneratedValue(strategy: 'IDENTITY')]
private ?int $vendorId = null;
#[Versioned]
#[Column(name: 'name', type: Types::STRING, length: 255, unique: true)]
private string $name;
public function getVendorId(): ?int
{
return $this->vendorId;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
} I can try to set up a full reproducer next week if this isn't enough to figure it out edit: not registering |
The only thing I can think of without building a full reproducer is that the config being passed by reference is causing an unexpected side-effect, adding a |
![]() |
Apparently, I can't use the same config key name (versioned) in both extensions since it looks like the I'll have to think about ways to improve this because the issue's really a symptom of a bigger design concern overall. |
I've changed the config key, so that conflict shouldn't come up anymore. |
Unfortunately doesn't seem to help. Exactly same thing as in #2825 (comment) |
I get what the mapping drivers are trying to do here (warn if you use |
Do you know when this new extension will be released ? |
5c9df80
to
c6701ba
Compare
I gave in, the revisionable extension will no longer use the |
Hello @mbabker, I wanted to kindly ask if you could share some visibility on the status of this PR. Do you have an idea of when it might be merged, and are there specific blockers preventing it from moving forward? We have several use cases that this PR directly addresses, and we are now reaching a stage where we’re considering whether to fork this PR to move ahead, rather than continue maintaining our own temporary solution with last doctrine updates. Before doing so, we would much prefer to align with the upstream direction. Would it also be possible to create a release candidate including this PR, so that we can test it directly against our projects? That would help us validate our scenarios and potentially provide feedback that benefits the broader community. Thank you again for your efforts and for any guidance you can provide. |
Co-authored-by: Javier Spagnoletti <[email protected]>
Co-authored-by: Javier Spagnoletti <[email protected]>
…ode for similar in loggable
…o be globally unique for all extensions
f6dd926
to
27becd3
Compare
I've gone as far with this as I can and it really just needs help testing from users. I don't have merge rights here so I can't personally do anything else with this right now other than keep it up-to-date, but in my mind since the bulk of it is a clone of the loggable extension with the adjustments in the data storage layer (switching from the DBAL's deprecated array type to JSON and switching from PHP serialized values to database normalized values through the MongoDB ODM and DBAL types APIs) it should be pretty much ready to go. The worst issues this PR's surfaced have been more design quirks with the library overall than bugs in the new extension, so to me that's also a good sign things should be ready to go. |
Thank you for the clarifications — we will make an attempt to switch over to your PR. We really appreciate the effort you’ve put into maintaining this branch and this feature for more than a year. Dear maintainers, would it be possible to consider merging this PR, perhaps under an experimental status as some frameworks do? This would allow the community to be aware of the feature’s current status, while still limiting the risks, which in this case appear to be quite minimal. At the same time, it would also allow us to move forward collectively on this topic. |
Ref: #2502
As the issue notes, the loggable extension is incompatible with ORM 4.x due to the removal of the array field type. The issue also has a patch for migrating to JSON, but because of the need for a data migration, it's not a patch that can be easily dropped in. Enter the new revisionable extension.
Mostly the same thing as the loggable extension, except for changing the way the data is collected for the history object's data field. For the ORM, the data was stored as a serialized PHP array, which while it works, is less than optimal:
The new extension uses the mapping layers of the ORM and ODM to store the data in its database representation, and for the ORM, as a native JSON field:
Another benefit to introducing a new extension is that the old and new versions can live side-by-side and allows for a data migration so long as your log entry data can be unserialized back into PHP and its info mapped to a database value (https://gist.github.com/mbabker/1879a3e55feac953e23cb2f025654052 was something I quickly hacked into the example app code as a proof of concept, a full-on tool could be built out using the logic here).
There are some other extras baked into this branch which generally help improve things too, including:
WrapperInterface
implementations (and@method
annotated on the interface itself to add to 4.0) to handle mapping values to PHP and database formats for each managerDifferences between the two extensions include:
prePersistLogEntry()
hook that existed in the loggable listener, the Doctrine event manager can be used here without needing to have an open classRevisionable
attribute instead ofLoggable
at the class level, and uses aKeepRevisions
attribute instead of theVersioned
attribute at the property level; for migrating, you're basically looking at a handful of lines changed no matter the mapping (changing a class import and annotation/attribute when using that mapping, XML goes from<gedmo:loggable/>
to<gedmo:revisionable/>
and<gedmo:versioned/>
to<gedmo:keep-revisions/>
)